﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using MultiViewer.Network;
using System.Threading;
using MultiViewer.Display;
using System.Diagnostics;

namespace MultiViewer.Display
{
    public partial class MultiViewerWindow : Window
    {
        /// <summary>Network node corresponding to this window</summary>
        private Node node;

        /// <summary>Holds information about current display settings</summary>
        private InitDisplayRequest initDisplayRequest;

        /// <summary>Row of display in grid of multidisplay. Rows are
        /// numerated from bottom to top, so row equals y coordinate</summary>
        /// <remarks>-1 for master node</remarks>
        private int displayRow;

        /// <summary>Column of display in grid of multidisplay.</summary>
        /// <remarks>-1 for master node</remarks>
        private int displayColumn;

        private Thread connectThread;

        public MultiViewerWindow()
        {
            StartConnecting();
        }

        /// <summary>Returns width of entire multidisplay width in pixels</summary>
        public int TotalWidth
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return initDisplayRequest.Width * initDisplayRequest.GridWidth;
            }
        }

        /// <summary>Returns height of entire multidisplay width in pixels</summary>
        public int TotalHeight
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return initDisplayRequest.Height * initDisplayRequest.GridHeight;
            }
        }

        public int DisplayRow
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return displayRow;
            }
        }

        public int DisplayColumn
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return displayColumn;
            }
        }

        public int DisplayWidth
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return initDisplayRequest.Width;
            }
        }

        public int DisplayHeight
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return initDisplayRequest.Height;
            }
        }

        public int GridWidth
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return initDisplayRequest.GridWidth;
            }
        }

        public int GridHeight
        {
            get
            {
                if (initDisplayRequest == null)
                    throw new InvalidOperationException("Display is not initialized yet");
                return initDisplayRequest.GridHeight;
            }
        }

        private void StartConnecting()
        {
            connectThread = new Thread(ConnectThreadFunc);
            connectThread.IsBackground = true;
            connectThread.Start();
        }

        private void ConnectThreadFunc()
        {
            const int TryDelay = 1; // 1 second between tries
            while (true)
            {
                try
                {
                    node = Node.Start(((MultiViewerApplication)Application.Current).Arguments);
                    Dispatcher.BeginInvoke(new SyncInvoker(OnNodeConnected));
                    break;
                }
                catch (Exception exc)
                {
                    Trace.WriteLine("Cannot start network node: " + exc.Message);
                    Trace.WriteLine("Retrying in " + TryDelay + " seconds...");
                    Thread.Sleep(TryDelay * 1000);
                }
            }
        }

        /// <summary>This method is invoked on main thread when node
        /// is successfully connected to communication network. After this call 
        /// Node property is not null</summary>
        protected virtual void OnNodeConnected()
        {
            Trace.WriteLine(String.Format("Node {0} of {1} is connected.",node.NodeID,node.NodeCount));
            Node.AddMessageHandler<InitDisplayRequest>(Dispatcher, InitDisplayHandler);
        }

        protected virtual void InitDisplayHandler(InitDisplayRequest request, HandlerResult<object> result)
        {
            initDisplayRequest = request;
            if (Node.NodeID > 0)
            {
                displayRow = Utils.GetDisplayRow(Node.NodeID, request.GridWidth, request.GridHeight);
                displayColumn = Utils.GetDisplayColumn(Node.NodeID, request.GridWidth, request.GridHeight);
            }
            else
                displayRow = displayColumn = -1;
            if(result != null)
                result.SetSuccess(true);
        }

        /// <summary>Gets network node. Returns null if node is not connected</summary>
        public Node Node
        {
            get { return node; }
        }

        public void BeginSend<T>(int[] nodes, T request, SendResponseHandler responseHandler) where T : Message
        {
            if(node == null)
                throw new InvalidOperationException("Cannot Send when node is not connected");
            ThreadPool.QueueUserWorkItem(dummy =>
            {
                Response[] responses = node.Send(nodes, request);
                Dispatcher.BeginInvoke(new SyncInvoker(delegate()
                {
                    try
                    {
                        responseHandler(nodes, responses);
                    }
                    catch (Exception exc)
                    {
                        Trace.WriteLine("Error handling " + typeof(T).Name + " response: " + exc.Message);
                    }
                }));
            });
        }
    }

    public delegate void SyncInvoker();
    public delegate void SendResponseHandler(int[] nodes, Response[] responses);
}
